package simuAnnealing;

import java.util.ArrayList;

import parser.*;

public class SimulatedAnnealing {

	private final int ITERATIONS_FOR_INITIAL_TEMPERATURE = 100;

	// variables used by the algorithm
	private FloorPlan actualSolution;
	private FloorPlan neighborSolution;
	private FloorPlan finalSolution;
	private int chosenSolutionsCounter = 0;
	private int goodSolutionsCounter = 0;
	private int insideIterationsCounter = 0;
	private double temperature;
	private double delta;
	private boolean iterationsPerTemperature = true;
	private ArrayList<Double> midWayFinalSolution; // A list of all the solutions
	private ArrayList<Double> midWayActualSolution;
	SAconfigurations configurations; // Parameters (from user)
	private double alfa;

	public SimulatedAnnealing() {
		actualSolution = null;
		neighborSolution = null;
		finalSolution = null;
		chosenSolutionsCounter = 0;
		goodSolutionsCounter = 0;
		insideIterationsCounter = 0;
		temperature = 0.0;
		delta = 0.0;
		midWayFinalSolution = new ArrayList<Double>();
		midWayActualSolution = new ArrayList<Double>();
		alfa = 0.0;
	}

	public FloorPlan getSolution() {
		return finalSolution;
	}

	public void executeAlgorithm(FloorPlan initialSolution,
			SAconfigurations config) {
		configurations = config;
		midWayFinalSolution.add(initialSolution.calculatePowerFloorPlanCost());
		simulatedAnnealing(initialSolution);
	}

	public void simulatedAnnealing(FloorPlan solution) {

		alfa = configurations.getAlfa();
		actualSolution = solution.clone();
		finalSolution = actualSolution.clone();
		FloorPlan initialTempretureCalcHelperFP = solution.clone();
		temperature = calculateInitialTemperature(initialTempretureCalcHelperFP);

		while (temperature > 1) {
			while (iterationsPerTemperature) {

				insideIterationsCounter++;
				neighborSolution = actualSolution.clone().doRandomChange();
				delta = neighborSolution.calculatePowerFloorPlanCost()
						- actualSolution.calculatePowerFloorPlanCost();

				if (delta < 0) {
					actualSolution = neighborSolution.clone();
					chosenSolutionsCounter++;
					goodSolutionsCounter++;
					if (actualSolution.calculatePowerFloorPlanCost() < finalSolution
							.calculatePowerFloorPlanCost()) {
						midWayActualSolution.add(actualSolution
								.calculatePowerFloorPlanCost());
						finalSolution = actualSolution.clone();
						goodSolutionsCounter++;
					}
				} else {
					if (Math.random() < Math.exp(-delta / temperature)) {
						midWayActualSolution.add(actualSolution
							.calculatePowerFloorPlanCost());
						actualSolution = neighborSolution.clone();
						chosenSolutionsCounter++;
						goodSolutionsCounter++;
					}
				}
				updateNumberOfIterations();
			}
			midWayFinalSolution.add(finalSolution.calculatePowerFloorPlanCost());
			reduceTemperature();
			iterationsPerTemperature = true;
			insideIterationsCounter = 0;
			chosenSolutionsCounter = 0;
			goodSolutionsCounter = 0;
		}
		alfa = configurations.getAlfa();
	}

	/**
	 * Calculate the initial temperature.
	 * 
	 * @param solution
	 *            : the initial solution
	 * @return the initial temperature
	 */
	private double calculateInitialTemperature(FloorPlan solution) {

		int counter = 0;
		double deltaSum = 0;

		while (counter < ITERATIONS_FOR_INITIAL_TEMPERATURE) {
			double cost = solution.calculatePowerFloorPlanCost();
			deltaSum += Math.abs((solution.doRandomChange())
					.calculatePowerFloorPlanCost() - cost);
			counter++;
		}
		double temperature = (100 * (deltaSum / ITERATIONS_FOR_INITIAL_TEMPERATURE));

		return (temperature == 0 ? 10 : temperature);

	}

	/**
	 * Two ways to reduce temperature 1) Using the alfa factor 2) Using the alfa
	 * factor that changed dynamically by the beta factor
	 */
	private void reduceTemperature() {
		switch (configurations.getHowToReduceTemperature()) {
		case REDUCE_BY_FACTOR:
			temperature = alfa * temperature;
			break;
		case REDUCE_BY_DYNAMIC_FACTOR:
			temperature = alfa * temperature;
			alfa *= (configurations.getBeta());
			break;
		}
	}

	/**
	 * Three ways to stop the inside loop (iterations per temperature) 1) By a
	 * constant number given as a parameter from the user 2) By the number of
	 * the solutions the algorithm choose 3) By the number of the good solutions
	 * the algorithm choose
	 */
	private void updateNumberOfIterations() {
		switch (configurations.getIterationsChoosingType()) {
		case BY_A_CONSTANT_NUMBER:
			if (insideIterationsCounter == configurations
					.getNumberOfIterationsPerTemperature())
				iterationsPerTemperature = false;
			break;
		case BY_NUMBER_OF_CHOSEN_SOLUTIONS:
			if (chosenSolutionsCounter == configurations
					.getNumberOfChosenSolutions())
				iterationsPerTemperature = false;
			break;
		case BY_NUMBER_OF_GOOD_SOLUTIONS:
			if (goodSolutionsCounter == configurations
					.getNumberOfGoodSolutions())
				iterationsPerTemperature = false;
			break;
		}
	}

	public ArrayList<Double> getMidWayFinalSolution() {
		return midWayFinalSolution;
	}

	public void setMidWayFinalSolution(ArrayList<Double> midWaySolutions) {
		this.midWayFinalSolution = midWaySolutions;
	}
	
	public ArrayList<Double> getMidWayActualSolution() {
		return midWayActualSolution;
	}

	public void setMidWayActualSolution(ArrayList<Double> midWaySolutions) {
		this.midWayActualSolution = midWaySolutions;
	}

}
